package cn.okcode.slowjson.parser;

import cn.okcode.slowjson.Config;
import cn.okcode.slowjson.internal.BeanUtils;
import cn.okcode.slowjson.internal.JsonException;
import cn.okcode.slowjson.internal.TypeCastUtils;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.Map;

/**
 * 序列表java bean、map、list
 *
 * @author pyk13
 */
public final class Serializer {

    private final StringBuilder out = new StringBuilder();

    public String serialize(Object object) {
        doSerialize(object);
        return out.toString();
    }


    private void doSerialize(Object object) {
        if (object == null) {
            doSerializeNull();
            return;
        }
        Class<?> clazz = object.getClass();
        if (object instanceof Map) {
            doSerializeMap((Map<?, ?>) object);
        } else if (object instanceof Collection) {
            doSerializeCollection((Collection<?>) object);
        } else if (clazz.isArray()) {
            doSerializeArray(object);
        } else {
            doSerializePrimary(object);
        }
    }

    private void doSerializeObject(Object object) {
        if (object == null) {
            doSerializeNull();
        } else {
            out.append("{");
            int i = 0;
            Class<?> clazz = object.getClass();
            for (Field field : clazz.getDeclaredFields()) {
                Object value;

                int modifiers = field.getModifiers();
                if (!Modifier.isPublic(modifiers)) {
                    Method getter = BeanUtils.findGetter(clazz, field.getName());
                    if (getter == null) {
                        //私有变量需要getter方法暴露出来才会被序列化
                        continue;
                    }
                    value = BeanUtils.invoke(getter, object);
                } else {
                    value = BeanUtils.getValue(field, object);
                }

                if (i++ > 0) {
                    out.append(",");
                }

                doSerializeString(field.getName());
                out.append(":");

                doSerialize(value);
            }
            out.append("}");
        }
    }

    private void doSerializeArray(Object array) {
        out.append("[");

        int length = Array.getLength(array);
        for (int i = 0; i < length; i++) {
            if (i > 0) {
                out.append(",");
            }
            Object element = Array.get(array, i);
            doSerialize(element);
        }

        out.append("]");
    }

    private void doSerializeMap(Map<?, ?> map) {
        out.append("{");
        int i = 0;
        for (Map.Entry<?, ?> entry : map.entrySet()) {
            if (i++ > 0) {
                out.append(",");
            }
            if (!(entry.getKey() instanceof String)) {
                throw new JsonException("Map的key必须是String类型");
            }
            appendString(entry.getKey());
            out.append(":");
            doSerialize(entry.getValue());
        }
        out.append("}");
    }

    private void doSerializeCollection(Collection<?> collection) {
        out.append("[");
        int i = 0;
        for (Object element : collection) {
            if (i++ > 0) {
                out.append(",");
            }
            doSerialize(element);
        }
        out.append("]");
    }

    private void doSerializePrimary(Object object) {
        Class<?> clazz = object.getClass();
        String className = clazz.getName();
        switch (className) {
            case "byte":
            case "java.lang.Byte":
            case "short":
            case "java.lang.Short":
            case "int":
            case "java.lang.Integer":
            case "long":
            case "java.lang.Long":
                doSerializeLong(TypeCastUtils.castToLong(object));
                break;
            case "float":
            case "java.lang.Float":
            case "java.lang.Double":
            case "double":
                doSerializeDouble(TypeCastUtils.castToDouble(object));
                break;
            case "java.math.BigDecimal":
                doSerializeBigDecimal(TypeCastUtils.castToBigDecimal(object));
                break;
            case "java.lang.Boolean":
            case "boolean":
                doSerializeBool(TypeCastUtils.castToBoolean(object));
                break;
            case "char":
            case "java.lang.Character":
            case "java.lang.String":
                doSerializeString(TypeCastUtils.castToString(object));
                break;
            case "Timestamp":
                doSerializeTimeStamp(TypeCastUtils.castToTimestamp(object));
                break;
            case "java.util.Date":
                doSerializeDate(TypeCastUtils.castToDate(object));
                break;
            default:
                doSerializeObject(object);
                break;
        }
    }

    private void doSerializeLong(Long value) {
        appendValue(value);
    }

    private void doSerializeDouble(Double value) {
        appendValue(value);
    }

    private void doSerializeBigDecimal(BigDecimal value) {
        appendValue(value.toPlainString());
    }

    private void doSerializeString(String value) {
        appendString(value);
    }

    private void doSerializeBool(Boolean value) {
        appendValue(value);
    }

    private void doSerializeDate(Date value) {
        DateFormat format = new SimpleDateFormat(Config.DEFAULT_DATETIME_FORMAT);
        String str = format.format(value);
        appendValue(str);
    }

    private void doSerializeTimeStamp(Timestamp value) {
        DateFormat format = new SimpleDateFormat(Config.DEFAULT_DATETIME_FORMAT);
        String str = format.format(value);
        appendValue(str);
    }

    private void doSerializeNull() {
        out.append("null");
    }

    private void appendString(Object stringValue) {
        if (stringValue == null) {
            out.append("null");
        } else {
            out.append("\"");
            out.append(serializeString(stringValue.toString()));
            out.append("\"");
        }
    }

    private void appendValue(Object value) {
        if (value == null) {
            out.append("null");
        } else {
            out.append(value);
        }
    }

    public final static String[] ESCAPES = new String[128];

    static {
        ESCAPES['\0'] = "\\0";
        ESCAPES['\1'] = "\\1";
        ESCAPES['\2'] = "\\2";
        ESCAPES['\3'] = "\\3";
        ESCAPES['\4'] = "\\4";
        ESCAPES['\5'] = "\\5";
        ESCAPES['\6'] = "\\6";
        ESCAPES['\7'] = "\\7";
        ESCAPES['\b'] = "\\b";
        ESCAPES['\t'] = "\\t";
        ESCAPES['\n'] = "\\n";
        ESCAPES['\f'] = "\\f";
        ESCAPES['\r'] = "\\r";
        ESCAPES['\"'] = "\\\"";
        ESCAPES['\\'] = "\\\\";
        ESCAPES['\u000B'] = "\\v";
        //ESCAPES['/'] = "\\/";
    }

    public static String serializeString(String string) {
        StringBuilder builder = new StringBuilder((int) (string.length() + string.length() * 0.5));
        for (char ch : string.toCharArray()) {
            if (ESCAPES.length > ch && ESCAPES[ch] != null) {
                builder.append(ESCAPES[ch]);
            } else {
                builder.append(ch);
            }
        }
        return builder.toString();
    }
}
